home *** CD-ROM | disk | FTP | other *** search
/ MacFormat 1994 November / macformat-018.iso / Utility Spectacular / Developer / Venus / project_3D.cc < prev    next >
Encoding:
Text File  |  1994-07-26  |  17.3 KB  |  527 lines  |  [TEXT/ALFA]

  1. // This may look like C code, but it is really -*- C++ -*-
  2. /*
  3.  ************************************************************************
  4.  *
  5.  *                           Rendering of a 3D function
  6.  *
  7.  * Suppose we have a function z=f(x,y) specified as an image, where
  8.  * pixel intensity z at point (x,y) is the value of the function f(x,y).
  9.  * We assume the map (function) is periodic, i.e., f(x+n*Dx,y+m*Dy)=f(x,y)
  10.  * for each integer n and m; Dx and Dy are dimensions of the map. We assume
  11.  * Dx = Dy = D. In the following, we assume that x, y are always within
  12.  * [0,D-1].
  13.  *
  14.  * The coordinate system (x,y,z) is chosen so that x increases along
  15.  * the horizontal lines, z is the elevation, and y increases "in depth".
  16.  *
  17.  * We would use a perspective projection with the center (x0, y0, z0)
  18.  * and the projection plane y=y0+eye_focus. So, the viewer looks from the
  19.  * point (x0,y0,z0) straight "in depth".
  20.  * When performing projection, we project the points of the map with y within 
  21.  *         [y0+eye_focus,y0+sight_depth]
  22.  * In doing the projections, we scan the 3D map along the line with y=const
  23.  * from y=y0+sight_depth to y=y0+eye_focus, that is, from farther to closer.
  24.  *
  25.  * Thus, we need to project a volume {x in [0,D), y in [y0+eye_focus,y0+sight_depth],
  26.  * z in [0,Z)} into the view plane {u in (0,Du), v in (0,Dv)}
  27.  * View plane is associated with the eye focal plane (and fixed at the observer,
  28.  * that is, us). We use a perspective transformation: draw a line from a point
  29.  * (x1,y1,z1) of the volume to a view point (x0,y0,z0), and see where the line
  30.  * intersects the view plane y=y0+eye_focus.
  31.  * The equation for the line connecting (x1,y1,z1) with (x0,y0,z0) is
  32.  *    (x-x0)/(x1-x0) = (y-y0)/(y1-y0) = (z-z0)/(z1-z0)
  33.  * The line intersects the plane y=y0+eye_focus at a point
  34.  *    x = x0 + (x1-x0)*factor
  35.  *  z = z0 + (z1-z0)*factor
  36.  *  with factor = eye_focus/(y1-y0)
  37.  * To finish the translation into the view port coordinates, we need to know
  38.  * the projection of our eye: if we look straight into the horizon, that is,
  39.  * y1=infinity, we see a point (x0,y0) at the view plane. We want this point
  40.  * to be suitably located in the view plane, that is, having the view plane
  41.  * coordinates (u=Du/2, v=horizon). It gives us necessary transformation formulas
  42.  * from a point (x,y,z) into the point (u,v) of the view plane
  43.  *    u = Du/2 + (x-x0)*factor
  44.  *  v = horizon + (z-z0)*factor
  45.  *    where
  46.  *        factor = eye_focus/(y-y0)
  47.  * Note that y >= y0+eye_focus always, therefore y>y0. That is, we can't see
  48.  * (at least distinctly) objects very close to our eyes, let alone objects
  49.  * behind our eyes. So, for all y we consider, 0<factor<=1
  50.  *
  51.  * If we scan the map along the line y=const, then 'factor' remains constant
  52.  * during the scan. Of course, we can increment x by one and then find
  53.  * the corresponding point (u,v) on the view plane and draw it.
  54.  * However, since the view plane is what we are going to display, it's
  55.  * better to increment u by one and then work it out back to x, find
  56.  * z value at (x,y) and compute v.
  57.  *
  58.  * So, the algorithm to process the scanline y=const is as follows:
  59.  * 1. fix y=const within [y0+eye_focus,y0+sight_depth] and compute
  60.  *    factor=eye_focus/(y-y0)
  61.  * 2. compute xref = -D/2/factor + x0
  62.  * 3. for u=0 to D-1
  63.  * 4.     compute x=(round)xref
  64.  * 5.     find z=f(x,y) from the map
  65.  * 6.      compute v = factor*(z-z0) + horizon
  66.  * 7.     xref += 1/factor
  67.  *     endfor
  68.  *
  69.  * Note, that the 'factor' should be represented as a fraction, since 0<factor<=1
  70.  * Division and multiplications by the factor are fixed-point arithmetic
  71.  * operations; xref is also a fixed-point number, the others are integers.
  72.  * We also assume that Du (width of the view plane) is equal to D (width of
  73.  * the map), though it isn't so critical.
  74.  *
  75.  * Since we scan from larger y's to smaller y's (that is, from farther ahead
  76.  * to near, that is, towards the observer), then if a (u,v) point generated by
  77.  * the algorithm should obscure (u,v) point generated at an earlier pass
  78.  * (with larger y), it would just overwrite the old point. However, if we just
  79.  * draw (u,v) dots we compute we get a dotty picture. Note, that actually we see
  80.  * a segment of line (x,y,z)-(x1,y-dy,z1). If we just connect two points generated
  81.  * in two consecutive scans, (u,v1) and (u,vold), it wouldn't be always
  82.  * right, because the original line could've been obscured. Suppose that
  83.  * z0 (elevation of the observation point) is high enough (comparable with
  84.  * the hight of the tallest peaks). Then if v(u,y+dy) > v(u,y) for some
  85.  * u and dy>0, then we have a descending slope, and the line is visible.
  86.  * Otherwise the line is on ascending slope, and it couldn't be visible
  87.  * because it will be obscured by the descending slope (which should be
  88.  * closer to us). Note the line from (u,c1) to (u,vold) is a vertical one,
  89.  * and therefore, is very easy to draw (and clip, if necessary).
  90.  *
  91.  * When we create (u,v) map, we assign to the point (u,v) the intensity
  92.  * (color) of the f(x,y) point of the original map. We can use Gouraud
  93.  * interpolation in drawing the line.
  94.  * Generalization: draw boxes (quadraluzation), than we can increase the
  95.  * scanning step in u (or in y) in the algorithm above.
  96.  *
  97.  * For this particular program, we also draw a part of the map as it is
  98.  * (that is, a 2D picture) above the horizon: just to make clouds.
  99.  *
  100.  * Inspiration:
  101.  * Tim Clarke, tjc1005@hermes.cam.ac.uk
  102.  * Note, there are some typos in that post. And my notation is completely different
  103.  * from his.
  104.  *
  105.  ************************************************************************
  106.  */
  107.  
  108. #include "image.h"
  109. #include "window.h"
  110.  
  111. #define Debug 0
  112. const short eye_focus = 50;
  113. const short sight_depth = 200;
  114.  
  115. /*
  116.  *----------------------------------------------------------------------
  117.  *                An instance of a generic screen window to display
  118.  *                                 a "3D" image
  119.  */
  120.  
  121. class ProjectionWindow : public OffScreenWindow
  122. {
  123.     const IMAGE& map;                    // that specifies z=f(x,y)
  124.  
  125.     short x0, y0, z0;                    // Point of view
  126.     short hor;                            // Elevation of the horizon on the view plane
  127.     Boolean do_animation;
  128.     
  129.     void project(void);                    // Draw a projection in an offscreen
  130.                                         // world
  131.                                         // Draw clouds above the horizon
  132.     void draw_clouds(const int horizon, const int starting_y);
  133.     
  134.                                 // A class to handle one scanline of the view plane
  135.     struct OneViewScanline
  136.     {
  137.         const short width;
  138.         short * vs;
  139.         char  * color;
  140.         
  141.         OneViewScanline(const short _width);
  142.         ~OneViewScanline(void);    
  143.                                 // Put all zeros (for the final scanline)
  144.         void clear(void);
  145.         
  146.     };
  147.     
  148.     OneViewScanline scanline1, scanline2;
  149.     void draw_scanline(OneViewScanline& scanline, const short y);
  150.                                 // Connect two scanlines, sl0 to sl1, drawing
  151.                                 // vertical lines between corresponding points
  152.                                 // (if the lines are visible)
  153.     void connect_scanlines(OneViewScanline& sl0, OneViewScanline& sl1);
  154.  
  155.                                         // redefining some event handlers
  156.     virtual Boolean handle_key_down(const EventRecord& the_event);
  157.     virtual Boolean handle_null_event(const long event_time);
  158.     
  159. public:
  160.     ProjectionWindow(const IMAGE& image, const char * title);
  161.     ~ProjectionWindow(void)  {}
  162. };
  163.  
  164.  
  165.                                 // Creating a window that would have our picture
  166.                                 // displayed.
  167. ProjectionWindow::ProjectionWindow(const IMAGE& image, const char * title)
  168.     : OffScreenWindow(ScreenRect(rowcol(256,image.q_ncols())),title,256),
  169.       map(image),
  170.       scanline1(image.q_ncols()),
  171.       scanline2(image.q_ncols()),
  172.       do_animation(FALSE)
  173. {
  174.     x0 = map.q_ncols()/2;            // Pick up the initial observation point
  175.     y0=0;                            // somewhere in the middle
  176.     z0 = 200;
  177.     hor = 100;
  178.     
  179.     project();
  180.     do_animation = TRUE;
  181. }
  182.  
  183.  
  184.                                     // Handles key_down & auto_key events. Return FALSE
  185.                                     // if the window is to be closed down
  186.                                     // The key handled move the observer and/or
  187.                                     // change his direction of view (horizon)
  188.                                     // To the observer (that is, us) it looks like
  189.                                     // the entire scene moves.
  190. Boolean ProjectionWindow::handle_key_down(const EventRecord& the_event)
  191. {
  192.     //message("char pressed %ld",the_event.message & charCodeMask);
  193.     switch(the_event.message & charCodeMask)
  194.     {
  195.         case 11:                // PgUp
  196.              if( the_event.modifiers & optionKey )
  197.                if( hor < 250 )
  198.                  hor++;
  199.                else if( z0 < 250 )
  200.                  z0++;
  201.              break;
  202.              
  203.         case 12:                // PgDn
  204.              if( the_event.modifiers & optionKey )
  205.                if( hor > 5 )
  206.                  hor--;
  207.                else if( z0 > 0 )
  208.                  z0--;
  209.              break;
  210.              
  211.         
  212.         case 30:                // Arrow Up
  213.              if( (y0 +=2) >= map.q_nrows() )
  214.                y0 = 0;
  215.              break;
  216.              
  217.         case 31:                // Arrow Down
  218.              if( (y0 -=2) < 0 )
  219.                y0 = map.q_nrows()-1;
  220.              break;
  221.  
  222.         case 28:                // Arrow Left
  223.              if( (x0 -=4) < 0 )
  224.                x0 = map.q_ncols()-1;
  225.              break;
  226.  
  227.         case 29:                // Arrow Righ
  228.              if( (x0 +=4) >= map.q_ncols() )
  229.                x0 = 0;
  230.              break;
  231.              
  232.         default:
  233.              return FALSE;                // Other keys kill the application
  234.              
  235.     }
  236.     project();
  237.     refresh();
  238.     return TRUE;
  239. }
  240.  
  241.  
  242.                                     // Handles null events, when nothing happens for some
  243.                                     // time. Return FALSE when it's time to die
  244.                                     // This function changes the view point (that
  245.                                     // is, moves the scene) with the time
  246.                                     // (or with the mouse moves), and makes animation
  247.                                     // When the mouse button is pressed, the
  248.                                     // scene moves with the mouse; otherwise, it
  249.                                     // moves by itself.
  250. Boolean ProjectionWindow::handle_null_event(const long event_time)
  251. {
  252.   static long int prev_tick_count = 0;
  253.   static Point prev_mouse_loc;
  254.   
  255.   if( prev_tick_count == 0 )
  256.   {
  257.     prev_tick_count = TickCount();
  258.     GetMouse(&prev_mouse_loc);
  259.     return TRUE;
  260.   }
  261.   
  262.   if( !do_animation )
  263.     return TRUE;
  264.  
  265.   if( StillDown() )                    // If mouse button is kept pressed, the user
  266.   {                                    // wants to lead the way. Move the picture
  267.       Point new_point;                // as he moves the mouse
  268.       GetMouse(&new_point);
  269.       x0 += (new_point.h - prev_mouse_loc.h)*2;
  270.       y0 -= (new_point.v - prev_mouse_loc.v)*2;
  271.       prev_mouse_loc = new_point;
  272.   }
  273.   else if( TickCount() - prev_tick_count < 20 )
  274.     return TRUE;
  275.   else
  276.   {
  277.     prev_tick_count = TickCount();
  278.  
  279.     x0 += 4;                                // Move the scene - do animation
  280.     y0 += 4;
  281.   }
  282.   
  283.   if( x0 >= map.q_ncols())                    // Do some clipping
  284.     x0 = 0;
  285.   else if( x0 < 0 )
  286.     x0 = map.q_ncols()-1;
  287.   if( y0 >= map.q_nrows())
  288.     y0 = 0;
  289.   else if( y0 < 0 )
  290.     y0 = map.q_nrows()-1;
  291.  
  292.   project();
  293.   refresh();
  294.     
  295.   return TRUE;                            // Keep going
  296. }
  297.  
  298.  
  299.  
  300.                                 // Allocate data for one scanline of the view window
  301. ProjectionWindow::OneViewScanline::OneViewScanline(const short _width)
  302.     : width(_width)
  303. {
  304.                                     // Allocate memory in one chunk for both arrays
  305.     vs = (short *)malloc(width*(sizeof(vs[0])+sizeof(color[0])));
  306.     assert( vs != 0 );
  307.     color = (char *)vs + width*sizeof(vs[0]);
  308. }
  309.  
  310.                                 // Dispose of the arrays
  311. ProjectionWindow::OneViewScanline::~OneViewScanline(void)
  312. {
  313.     assert( vs != 0 );
  314.     delete vs;                        // color would be disposed of, too
  315. }
  316.  
  317.                                 // Put all zeros (for the final scanline)
  318. void ProjectionWindow::OneViewScanline::clear(void)
  319. {
  320.     memset((void *)vs,0,width*sizeof(vs[0]));
  321.     memset((void *)color,0,width*sizeof(color[0]));
  322. }
  323.  
  324.  
  325.                                 // Draw a scanline, i.e. line of const y for
  326.                                 // all u's within 0..width-1
  327.                                 // see formulas above
  328.                                 // For factor, invfactor, xref we use a fixed-
  329.                                 // point arithmetic with 8-bit implied fraction
  330. void ProjectionWindow::draw_scanline(OneViewScanline& scanline, const short y)
  331. {
  332.     int factor = (eye_focus<<8)/(y-y0);
  333.     int invfactor = ((long)(y-y0)<<8)/eye_focus;
  334.                                     // We know that xref = -D/2/factor + x0
  335.                                     // But we want to keep xref positive, moreover,
  336.                                     // within [0,D-1]. Since the map is periodic
  337.                                     // with period D, then we can write
  338.                                     // xref = floor(1/2/factor)*D - (D/2)*(1/factor) + x0
  339.                                     // or,
  340.                                     // xref = D( floor(1/2/factor) - (1/2/factor) ) + x0
  341.                                     //      = -D*frac(1/2/factor) + x0
  342.                                     // In fixed point 8-bit fraction arithmetic, it's elementary
  343.                                     // to separate integer and fractional part of
  344.                                     // the invfactor/2
  345.                                     // It's easy to see that xref we got is within the desired
  346.                                     // interval, give or take one D.
  347.     int xref = (x0<<8) - scanline.width * ( (unsigned char)(invfactor >> 1) );
  348.                                     // Make sure xref is within [0,width) in
  349.                                     // the fixed-point 8-bit fraction arithmetic
  350.     const int width_fp = scanline.width<<8;
  351.     if( xref >= width_fp )
  352.         xref -= width_fp;
  353.     if( xref < 0 )
  354.         xref += width_fp;
  355.      
  356.     
  357.     short y_map = y;                // Clip y to the map
  358.     while( y_map >= map.q_nrows() )
  359.         y_map -= map.q_nrows();
  360.  
  361. #if Debug
  362.     message("draw scanline y=%d, factor %d, xref %d",y,factor,xref);
  363. #endif
  364.     register short * vp;
  365.     register char * cp;
  366.     for(vp=scanline.vs, cp = scanline.color; vp<&scanline.vs[scanline.width]; )
  367.     {
  368.         int x = xref>>8;
  369.         int z = map(y_map,x);
  370.         *vp++ = ((factor*(z-z0))>>8) + hor;
  371.         *cp++ = z;
  372. #if Debug
  373.         if( vp - scanline.vs < 5 )
  374.           message("map(%d,%d) is %d, projected to v=%d",y_map,x,z,*(vp-1));
  375. #endif
  376.         if( (xref += invfactor) >= width_fp )
  377.           xref -= width_fp;
  378.     }
  379. }
  380.  
  381.  
  382.                                     // Draw a projection in an offscreen
  383.                                     // graf world
  384. void ProjectionWindow::project(void)
  385. {
  386. //    SetOffscreenWorld offscreen_world(*this);
  387.  
  388. //    ScreenRect sub_horizon(q_bounds());
  389. //    sub_horizon.top +=40; //= sub_horizon.bottom-z0;
  390. //    sub_horizon.print("sub horizon");
  391. //    EraseRect(sub_horizon);
  392.  
  393.     draw_clouds(hor,eye_focus);
  394.  
  395.     OneViewScanline * curr_scanline = &scanline1,
  396.                     * prev_scanline = &scanline2;
  397.                                                 // note curr_scanline ^ pointer_diff
  398.                                                 // gives prev_scanline, and
  399.                                                 // vice versa
  400.     int pointer_diff = (long int)curr_scanline ^ (long int)prev_scanline;
  401.     
  402.     draw_scanline(*prev_scanline,y0+sight_depth);
  403.  
  404.     register int y;
  405.     for(y=y0+sight_depth-1; y >= y0 + eye_focus; y--)
  406.     {
  407. #if Debug
  408.         if((y0+sight_depth-1)-y > 3 )
  409.           exit(0);
  410. #endif
  411.         draw_scanline(*curr_scanline,y);
  412.         
  413.         connect_scanlines(*prev_scanline,*curr_scanline);
  414.         
  415.                                             // exchange pointers
  416.         curr_scanline = (OneViewScanline*)((long int)curr_scanline ^ pointer_diff);
  417.         prev_scanline = (OneViewScanline*)((long int)prev_scanline ^ pointer_diff);
  418. //        (long int&)prev_scanline ^= pointer_diff;
  419.     }
  420. }
  421.  
  422.                                 // Connect two scanlines, sl0 to sl1, drawing
  423.                                 // vertical lines between the corresponding points
  424.                                 // (if the lines are visible)
  425. void ProjectionWindow::connect_scanlines(
  426.     OneViewScanline& sl0, OneViewScanline& sl1)
  427. {
  428.     PixMapHandle pixmap = get_pixmap();
  429.  
  430.     assert( LockPixels(pixmap) );
  431.     
  432.     char * pixp = GetPixBaseAddr(pixmap);
  433.     register int u;
  434.     for(u=0; u<width(); u++)                    // Draw a vertical line (u,v)-(u,vold)
  435.     {                                            // (providing it's visible)
  436.       int vold = sl0.vs[u];
  437.       int v    = sl1.vs[u];
  438.       if( v > vold )
  439.         continue;                    // The line is on the ascending slope and *will* be obscured later
  440.                                     // Keep in mind that v <= vold now
  441.       if( v >= height() || vold <= 0 )
  442.         continue;                    // means both v and vold are out-of-picture
  443.  
  444.       if( v < 0 )
  445.         v = 0;
  446.                                     // Now, 0<=v <= vold
  447.       int z0    = (unsigned char)sl0.color[u]<<4;    // For the purpose of color interpolation
  448.       int z     = (unsigned char)sl1.color[u]<<4;    // we use fixed-point arithmetic with 4 bit
  449.       int stretch = v == vold ? 0 : (z0-z)/(vold-v);// implicit fraction
  450. #if Debug
  451.     if(u<4)
  452.       message("u=%ld drawing from v=%ld to vold=%ld at zs %lx-%lx",u,v,vold,z>>4,z0>>4);
  453. #endif
  454.                                       // draw a line from v to vold
  455.                                       // Note that v on mac goes from top to bottom,
  456.                                       // that is, we need a flip
  457.       char * pixp_beg = pixp + u + (height()-1-v) * bytes_per_row();
  458. //      *pixp_beg = 126;
  459.       for(; v <= (vold > height()-1 ? height()-1 : vold); v++,pixp_beg -= bytes_per_row())
  460.           *pixp_beg = z>>4,
  461.           z += stretch;
  462.     }
  463.  
  464.     UnlockPixels(pixmap);
  465. }
  466.  
  467.                                 // Draw clouds above the horizon line
  468.                                 // using the same map
  469.                                 // Simply speaking, just copy a rectangular
  470.                                 // segment of the map with y from 0 to
  471.                                 // horizon into the offscreen world
  472.                                 // Below the horizon, fill everything (I mean,
  473.                                 // the offscreen world) with the
  474.                                 // background "pixel"
  475. void ProjectionWindow::draw_clouds(const int horizon, const int starting_y)
  476. {
  477.     const unsigned char background_pixel = 124;
  478.     
  479.     assert( horizon >= 0 && horizon < height() );
  480.     
  481.     PixMapHandle pixmap = get_pixmap();
  482.     
  483.     assert( LockPixels(pixmap) );
  484.     
  485.     char * pixp = GetPixBaseAddr(pixmap);
  486.     char * pixp_stop = pixp + (height()-horizon)*bytes_per_row();
  487.     register int i;                                // "Draw" the image
  488.     for(i=starting_y; pixp<pixp_stop; i++, pixp += bytes_per_row()-width())
  489.     {                                            // Note, bytes_per_row is generally GREATER
  490.       register int j;                            // than width, and is always a multiple of 4
  491.       if( i >= map.q_nrows() )
  492.         i = 0;                                    // make wrap-around
  493.       for(j=0; j<width(); j++)
  494.            *pixp++ = map(i,j);
  495.     }
  496.  
  497.     pixp_stop += horizon*bytes_per_row();        // Continue till the end of pixmap
  498.     for(; pixp<pixp_stop; pixp += bytes_per_row()-width())
  499.     {                                            // Note, bytes_per_row is generally GREATER
  500.       register int j;                            // than width, and is always a multiple of 4
  501.       for(j=0; j<width(); j++)
  502.            *pixp++ = background_pixel;
  503.     }
  504.  
  505.     UnlockPixels(pixmap);
  506. }
  507.  
  508.  
  509. /*
  510.  *----------------------------------------------------------------------
  511.  *                        Routing display function
  512.  */
  513.  
  514. void project_3D(const IMAGE& image, const char * title)
  515. {
  516. #if 0
  517.     IMAGE image(image1);
  518.     register int i,j;
  519.     int ncols = image.q_ncols();
  520.     for(j=-20; j<20; j++)
  521.       for(i=-5; i<5; i++)
  522.         image(eye_focus+i,j+ncols/2) = 128 - 5*abs(i+j),
  523.         image(eye_focus+40+i,j+ncols/2) = 180 - 7*abs(i+j);
  524. #endif
  525.     ProjectionWindow map_projection(image,title);
  526.     map_projection.handle();
  527. }